package whf.framework.web.tag;

import java.io.IOException;
import java.io.Serializable;
import java.net.URLEncoder;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.jsp.JspException;

import whf.framework.datasource.DataRow;
import whf.framework.datasource.DataSource;
import whf.framework.datasource.DataSourceException;
import whf.framework.datasource.impl.CollectionDataSource;
import whf.framework.datasource.impl.QueryServiceDataSource;
import whf.framework.datasource.impl.SingleDataSource;
import whf.framework.entity.Entity;
import whf.framework.entity.PropertyTypeEnum;
import whf.framework.exception.PropertyException;
import whf.framework.ext.DictManager;
import whf.framework.ext.DictManagerImp;
import whf.framework.ext.entity.DictItem;
import whf.framework.i18n.ApplicationResource;
import whf.framework.log.Log;
import whf.framework.log.LogFactory;
import whf.framework.meta.Meta;
import whf.framework.meta.MetaFactory;
import whf.framework.meta.entity.IMeta;
import whf.framework.meta.entity.IProperty;
import whf.framework.meta.entity.View;
import whf.framework.report.ReportUtils;
import whf.framework.security.UserContext;
import whf.framework.service.Service;
import whf.framework.util.AttributeStringBuilder;
import whf.framework.util.Base64;
import whf.framework.util.BeanFactory;
import whf.framework.util.BeanUtils;
import whf.framework.util.DateUtils;
import whf.framework.util.ParamEncoder;
import whf.framework.util.ParamUtils;
import whf.framework.util.PermissionUtils;
import whf.framework.util.StringUtils;
import whf.framework.util.TypeUtils;
import whf.framework.util.Utils;
import whf.framework.validate.ValidateErrors;
import whf.framework.web.Constants;
import whf.framework.web.WebContextHolder;
import whf.framework.web.tag.enums.DisplayStyle;
import whf.framework.web.tag.enums.NavPosition;
import whf.framework.web.tag.enums.NavStyle;
import whf.framework.web.tag.enums.PkStyle;
import whf.framework.web.tag.ext.ICollectionPropertyDisplay;
import whf.framework.web.tag.ext.TableAttributes;
import whf.framework.web.tag.ext.TableRowHTMLDecorator;
import whf.framework.web.tag.ext.TableTagConstants;
import whf.framework.web.tag.wrapper.IDisplayAppendWrapper;
import whf.framework.web.tag.wrapper.IDisplayWrapper;

/**
 * 
 * @author wanghaifeng
 * 
 */
@SuppressWarnings("unchecked")
public class TableTag extends HtmlTag implements TableTagConstants {
	private final static long serialVersionUID = 0;

	private static Log log = LogFactory.getLog(TableTag.class);
	
	// ----------------------条件------------------------------
	/**
	 * 查询条件字符串,格式参考IDAO.findByPropertyString
	 */
	private String queryString;

	/**
	 * 查询参数，配合queryString使用
	 */
	private Object[] queryParameters;
	/**
	 * 用于查找meta，
	 */
	private String boClassName;

	/**
	 * 对于集合属性的展现风格
	 */
	private ICollectionPropertyDisplay collectionPropertyDisplay = DEFAULT_COLLECTION_PROPERTY_DISPLAY;
	
	/**
	 * 行属性分析器，根据不同的行属性返回不同的属性值
	 * @property TableRowDecorator:rowPropertyParser
	 */
	private TableRowHTMLDecorator rowDecorator = DEFAULT_ROW_DECORATOR;
	
	/**
	 * 将对象封装，其类必须提供一个带一个参数的构造函数；
	 * 可用于将一个对象的属性转变为另一个对象的属性；
	 * 具体的先集成对应的业务类型，然后覆盖特定的一些类型！
	 */
	private Class wrapClass;
	
	/**
	 * 使用其中的输出方法输出对象，而不是使用tabletag中的描述
	 * @property IDisplayWrapper:displayWrapper
	 */
	private IDisplayWrapper displayWrapper;
	
	/**
	 * 在displayStyle=table有效
	 * 如果非空，给每一行最后位置加上一个独立的列，并打印出rowExt，可以带参数，其参数格式为$propertyName的格式，个数不限
	 * 通常用于解决每行按钮的情况，可以支持多个按钮格式如<div><button ><button>...</div>
	 * 如果是功能按钮建议不实用这个，因为在这里意味着权限控制无法达到；
	 * @property String:rowExt
	 */
	private String rowExt;
	
	/**
	 * add a prefix for control, so you can distinguish when multi table tag in a jsp
	 */
	private String namePrefix = "";
	
	/**
	 * if a edit a table with multi records
	 * use this can unique every control
	 */
	private boolean pkSuffix;

	//
	/**
	 * 主键风格
	 */
	private PkStyle pkStyle;
	/**
	 * table或者form,缺省取table
	 */
	private DisplayStyle displayStyle;
	
	/**
	 * 是否可编辑
	 */
	private boolean editable;
	
	/**
	 * 显示列数，对于displayStyle=form时有效
	 */
	private int displayColumnCount;
	
	private String caption;
	
	private String footer;
	
	/**
	 * 显示数据宽度，对于displayStyle=form时有效
	 * 使用百分比的百分数
	 */
	private String displayCellWidth;
	
	/**
	 * 是否隐藏title行，true：隐藏； false：不隐藏
	 * displayStyle=table时有效
	 * @property boolean:hideTitleRow
	 */
	private boolean hideTitleRow;
	
	/**
	 * 使用不同的后缀将可以获取不同的css class
	 * @property String:cssClassSuffix
	 */
	private String cssClassSuffix;
	// ----------------------导航------------------------------
	private NavPosition navPosition;
	/**
	 * one-to-one use: first, prev, next, last
	 * select use this:<select name='select_page_number'>
	 * number use this:<a href=''>1</a>
	 */
	private NavStyle navStyle;
	
	private boolean cacheTableAttributes = true;
	
	/**
	 * 视图代码
	 * @property String:view
	 */
	private String view;
	
	private boolean useViewProperties = false;

	/**
	 * can not be cached because it in a tag
	 * should be the data only should display in jsp
	 */
	private DataSource<DataRow> dataSource;
	
	/**
	 * @Property
	 * a datasource come from a attribute in request or session
	 * you can use a property in an object in request or a session
	 * just like Object.property1.property2,so system will search "Object.property1.property2"
	 * if not found search "Object", if found get the property1 and property2 as the dataSource
	 * 
	 * can use with useStrutsForm together
	 * at this time, if dataSourceName startsWith a "."(dot is), it will be the struts form's property
	 * eg.
	 * < ...... boClassName="Group" useStrutsForm="true" dataSourceName=".users" /> 
	 * it means the Group.users property
	 * 
	 */
	private String dataSourceName;

	/**
	 * 是否使用Struts表单对象
	 * if true, the datasourceName will be STRUTS-FORMNAME
	 */
	private boolean useStrutsForm;

	/**
	 * 与IProperty中的对应，只有这边的大于或等于才可以看见
	 */
	private int visibleLevel;
	
	/**
	 * 只有列中定义的可编辑级别大于或等于此级别才可以编辑
	 */
	private int editableLevel;
	
	/**
	 * 为性能用,只有当loadLazyData = true时,才提取lazy数据
	 * @property boolean:loadLazyData
	 */
	private boolean loadLazyData;
	
	//打印,预览等按钮
	/**
	 * 允许预览按钮
	 * @property boolean:enablePreview
	 */
	private boolean enablePreview = true;
	
	/**
	 * 允许导出pdf按钮
	 * @property boolean:enableExportPdf
	 */
	private boolean enableExportPdf = true;
	
	/**
	 * 允许导出xls按钮
	 * @property boolean:enableExportXls
	 */
	private boolean enableExportXls = true;
	
	/**
	 * 允许定制列
	 * @property boolean:enableCustomizeColumns
	 */
	private boolean enableCustomizeColumns = true;
	
	/**
	 * 允许设计视图
	 * @property boolean:enableCustomizeColumns
	 */
	private boolean enableDesignView = true;
	
	// -----------------------------------------transient variables------------------------------------------------
	private int start;

	private int end;

	private int totalRowCount;

	private int pageSize = DEFAULT_PAGE_SIZE;

	/**
	 * 当前行号计数
	 */
	private int rowNumber = 1;

	/**
	 * 页号
	 */
	private int pageNumber;

	
	/**
	 * property:meta
	 * maybe a whf.framework.meta.entity.Object or whf.framework.meta.entity.View
	 */
	private IMeta boMeta;

	private TableModel tableModel;

	private TableRow currentRow;
	
	private String varTotals;

	/**
	 * @property boolean:lastIteration
	 */
	private boolean lastIteration;

	/**
	 * @property boolean:doAfterBodyExecuted
	 */
	private boolean doAfterBodyExecuted;

	private boolean includedNavigationTag;

	private ParamEncoder paramEncoder;
	
	/**
	 * if you use multi this tag in a jsp page, so this required
	 * system can distinguish each
	 */
	private String uid;
	//
	/**
	 * @param enableExportPdf The enableExportPdf to set.是否允许导出pdf按钮
	 */
	public void setEnableExportPdf(boolean enableExportPdf) {
		this.enableExportPdf = enableExportPdf;
	}

	/**
	 * @param enableExportXls The enableExportXls to set. 是否允许导出excel按钮
	 */
	public void setEnableExportXls(boolean enableExportXls) {
		this.enableExportXls = enableExportXls;
	}

	/**
	 * @param enablePreview The enablePreview to set. 是否允许预览按钮
	 */
	public void setEnablePreview(boolean enablePreview) {
		this.enablePreview = enablePreview;
	}

	/**
	 * @param enableCustomizeColumns The enableCustomizeColumns to set. 是否允许定制列
	 */
	public void setEnableCustomizeColumns(boolean enableCustomizeColumns) {
		this.enableCustomizeColumns = enableCustomizeColumns;
	}

	/**
	 * @param enableDesignView the enableDesignView to set
	 */
	public void setEnableDesignView(boolean enableDesignView) {
		this.enableDesignView = enableDesignView;
	}
	
	public void setIncludedNavigationTag(boolean includedNavigationTag) {
		this.includedNavigationTag = includedNavigationTag;
	}

	/**
	 * @param boClassName 设置业务对象类名
	 */
	public void setBoClassName(String boClassName) {
		this.boClassName = boClassName;
	}

	/**
	 * @param cacheColumns The cacheColumns to set.
	 */
	public void setCacheTableAttributes(boolean cacheTableAttributes) {
		this.cacheTableAttributes = cacheTableAttributes;
	}

	/**
	 * 设置过滤字符串，并使用扩展字符串替换；
	 * @modify wanghaifeng Sep 13, 2006 10:25:43 PM
	 * @param queryString
	 */
	public void setQueryString(String queryString) {
		this.queryString = queryString;
	}

	public void setQueryParameters(Object[] queryParameters){
		this.queryParameters = queryParameters;
	}
	
	/**
	 * @param dataSource 数据源
	 */
	public void setDataSource(DataSource<DataRow> dataSource) {
		this.dataSource = dataSource;
	}
	
	/**
	 * @param dsName 数据源名称，从一切的数据源中查找这个名称的对象
	 */
	public void setDataSourceName(String dsName){
		this.dataSourceName = dsName;
	}

	/**
	 * @param useStrutsForm 是否使用struts中表单数据作为数据源，在struts中有用
	 */
	public void setUseStrutsForm(boolean useStrutsForm) {
		this.useStrutsForm = useStrutsForm;
	}
	
	/**
	 * @deprecated
	 * @param ext 设置是否扩展，现在已经被framework:ext tag替代
	 */
	public void setRowExt(String ext){
		this.rowExt = ext;
	}
	
	/**
	 * @param className 包装类
	 */
	public void setWrapClass(String className){
		if(StringUtils.isEmpty(className)) return;
		this.wrapClass = Utils.getClassByClassName(className);
	}

	/**
	 * 根据输入的内容，尽量的构造一个DisplayWrapper<br>
	 * 对象的包装类，包装对象呈其它展现或者改变数据源，其最终是获取一个IDisplayWrapper的实例；<br>
	 * 当前只在displayStyle=block中有效
	 * @param blockDisplayWrapper The blockDisplayWrapper to set.
	 */
	public void setDisplayWrapper(Object displayWrapper) {
		if(displayWrapper == null) return ;
		if(displayWrapper instanceof IDisplayWrapper){
			this.displayWrapper = (IDisplayWrapper)displayWrapper;
		} else if(displayWrapper instanceof String){
			this.displayWrapper = (IDisplayWrapper)Utils.constructBean(Utils.getClassByClassName(displayWrapper.toString()));
		} else if(displayWrapper instanceof Class){
			this.displayWrapper = (IDisplayWrapper)Utils.constructBean((Class)displayWrapper);
		} else {
			throw new IllegalArgumentException("DisplayWrapper must instanceof IDisplayWrapper or String or Class!");
		}
	}
	
	/**
	 * 设置行属性分析器<br>
	 * 主要用在一个特效中,如一些特殊的对象让背景颜色改变等
	 * @modify wanghaifeng Apr 9, 2007 1:27:19 PM
	 * @param parser 可以是ITableRowPropertyParser的实例，
	 * 或者一个ITableRowPropertyParser子对象的类名，
	 * 或者ITableRowPropertyParser子对象的类
	 * 其它的不行
	 */
	public void setRowDecorator(Object parser){
		if(parser == null) return;
		if(parser instanceof TableRowHTMLDecorator){
			this.rowDecorator = (TableRowHTMLDecorator)parser;
		}else if(parser instanceof String){
			this.rowDecorator = (TableRowHTMLDecorator)Utils.constructBean(Utils.getClassByClassName(parser.toString()));
		} else if(parser instanceof Class){
			this.rowDecorator = (TableRowHTMLDecorator)Utils.constructBean((Class)parser);
		} else {
			throw new IllegalArgumentException("RowPropertyParser must be instanceof ITableRowPropertyParser or String or Class");
		}
	}

	/**
	 * 设置导航条位置
	 * @modify wanghaifeng Apr 9, 2007 1:27:42 PM
	 * @param pos
	 */
	public void setNavPosition(String pos) {
		if(StringUtils.equalsIgnoreCase(pos,"top")){
			this.navPosition = NavPosition.TOP;
		} else if(StringUtils.equalsIgnoreCase(pos, "bottom")){
			this.navPosition = NavPosition.BOTTOM;
		} else if(StringUtils.equalsIgnoreCase(pos, "both")){
			this.navPosition = NavPosition.BOTH;
		}else{
			this.navPosition = NavPosition.NONE;
		}
	}

	/**
	 * @param hideTitleRow The hideTitleRow to set.
	 * 是否隐藏标题行
	 */
	public void setHideTitleRow(boolean hideTitleRow) {
		this.hideTitleRow = hideTitleRow;
	}

	/**
	 * 设置导航风格
	 * @modify wanghaifeng Apr 9, 2007 1:27:59 PM
	 * @param style
	 */
	public void setNavStyle(String style) {
		if(StringUtils.equalsIgnoreCase(style, "select")){
			this.navStyle = NavStyle.SELECT;
		} else if(StringUtils.equalsIgnoreCase(style, "number")){
			this.navStyle = NavStyle.NUMBER;
		} else{
			this.navStyle = NavStyle.ONE_2_ONE;
		}
	}

	/**
	 * @param pageSize 设置页面大小
	 */
	public void setPageSize(int pageSize) {
		this.pageSize = pageSize;
	}
	
	/**
	 * @param start 对象开始位置
	 */
	public void setStart(int start){
		this.start = start;
	}

	public void setTotalRowCount(int totalRowCount) {
		this.totalRowCount = totalRowCount;
	}

	/**
	 * @param caption 手动显示标题头，另CaptionTag可以参考
	 */
	public void setCaption(String caption) {
		this.caption = caption;
	}

	/**
	 * @param footer 手动设置页脚
	 */
	public void setFooter(String footer) {
		this.footer = footer;
	}

	/**
	 * @param displayColumnCount 显示列数<br>
	 * 当displayStyle=form or block时有效，表示显示内容的列数
	 */
	public void setDisplayColumnCount(int displayColumnCount) {
		this.displayColumnCount = displayColumnCount;
	}

	/**
	 * @param displayStyle 设置显示风格
	 * form:表单风格，多行显示一个对象<br>
	 * block:块风格，一行显示多个对象<br>
	 * table:缺省的，一行显示一个对象
	 */
	public void setDisplayStyle(String displayStyle) {
		if(StringUtils.equalsIgnoreCase(displayStyle, "form")){
			this.displayStyle = DisplayStyle.FORM;
		} else if(StringUtils.equalsIgnoreCase(displayStyle, "block")){
			this.displayStyle = DisplayStyle.BLOCK;
		} else{
			this.displayStyle = DisplayStyle.TABLE;
		}
	}

	/**
	 * @param cssClassSuffix The cssClassSuffix to set.
	 * css后缀，用作多表现风格时使用
	 */
	public void setCssClassSuffix(String cssClassSuffix) {
		this.cssClassSuffix = cssClassSuffix;
	}

	/**
	 * @param visibleLevel 显示级别
	 */
	public void setVisibleLevel(int visibleLevel) {
		this.visibleLevel = visibleLevel;
	}

	public void setEditableLevel(int editableLevel){
		this.editableLevel = editableLevel;
	}
	
	/**
	 * @param loadLazyData The loadLazyData to set.
	 * 是否加载惰性数据，对于需要显示集合属性时设置为true
	 */
	public void setLoadLazyData(boolean loadLazyData) {
		this.loadLazyData = loadLazyData;
	}
	
	/**
	 * @param editable 设置是否可以编辑，当displayStyle='form' or 'table'时有效
	 */
	public void setEditable(boolean editable) {
		this.editable = editable;
	}

	public void setVarTotals(String varTotals) {
		this.varTotals = varTotals;
	}
	
	private void setCurrentRow(TableRow row){
		this.currentRow = row;
		if(row != null)
			request().setAttribute("currentRow", row.getRowObject());
		else
			request().removeAttribute("currentRow");
	}

	/**
	 * @param style 主键展现风格
	 */
	public void setPkStyle(String style) {
		if(StringUtils.equalsIgnoreCase(style, "checkbox")){
			this.pkStyle = PkStyle.CHECKBOX;
		} else if(StringUtils.equalsIgnoreCase(style,"radio")){
			this.pkStyle = PkStyle.RADIO;
		} else if(StringUtils.equalsIgnoreCase(style, "hide")){
			this.pkStyle = PkStyle.HIDDEN;
		} else if(StringUtils.equalsIgnoreCase(style, "checkbox_counter")) {
			this.pkStyle = PkStyle.CHECKBOX_AND_COUNTER;
		} else if(StringUtils.equalsIgnoreCase(style, "radio_counter")) {
			this.pkStyle = PkStyle.RADIO_AND_COUNTER;
		} else {
			this.pkStyle = PkStyle.COUNTER;
		}
	}

	/**
	 * @param pkPrefix 设置对象前缀
	 */
	public void setNamePrefix(String pkPrefix) {
		this.namePrefix = pkPrefix;
	}

	/**
	 * @param pkSuffix 主键是否作为对象后缀
	 */
	public void setPkSuffix(boolean pkSuffix) {
		this.pkSuffix = pkSuffix;
	}

	/**
	 * @param displayCellWidth 显示数据宽度，对于displayStyle=form时有效
	 */
	public void setDisplayCellWidth(String displayCellWidth) {
		this.displayCellWidth = displayCellWidth;
	}

	// ------------------------------------------------------------------------
	private String cssClass(String cssClass){
		if(this.cssClassSuffix == null){
			return cssClass;
		} else {
			return cssClass + this.cssClassSuffix;
		}
	}
	
	public String getVarTotals() {
		return varTotals;
	}

	/**
	 * @return 模型数据
	 */
	public TableModel getTableModel() {
		return tableModel;
	}

	/**
	 * @return 获取当前数据行，多用于嵌套Tag
	 */
	public TableRow getCurrentRow() {
		return currentRow;
	}
	
	/**
	 * @return 是否第一次循环，对于嵌套tag有用
	 */
	public boolean isFirstIteration() {
		return this.rowNumber == 1;
	}

	/**
	 * @return 是否最后一次循环，对于嵌套Tag有用
	 */
	public boolean isLastIteration() {
		return this.lastIteration;
	}

	/**
	 * @return  是否包含导航Tag
	 */
	public boolean isIncludedNavigationTag() {
		return includedNavigationTag;
	}
	
	public void setId(String id) {
		super.setId(id);
		this.uid = id;
	}

	private String getUid() {
		if(StringUtils.isEmpty(this.uid)){
			this.uid = this.boClassName;
		}
		if (this.paramEncoder == null) {
			this.paramEncoder = new ParamEncoder(uid);
		}
		return this.paramEncoder.getParameterIdentifier();
	}

	public IMeta getBoMeta() {
		return boMeta;
	}

	/**
	 * @param boMeta 直接设置元数据
	 */
	public void setBoMeta(IMeta boMeta) {
		this.boMeta = boMeta;
	}
	
	/**
	 * @param view	视图代码，通过视图代码获取元数据
	 * 如果有视图，则优先于对象
	 */
	public void setView(String view){
		this.view = view;
	}

	/**
	 * @param useViewProperties
	 */
	public void setUseViewProperties(boolean useViewProperties) {
		this.useViewProperties = useViewProperties;
	}

	/**
	 * 对于集合属性的展现风格
	 */
	public void setCollectionPropertyDisplay(
			ICollectionPropertyDisplay collectionPropertyDisplay) {
		this.collectionPropertyDisplay = collectionPropertyDisplay;
	}

	/**
	 * 对tag传输的参数额外处理，用以识别是tag的参数和页面的参数
	 * @param parameterName
	 * @return
	 */
	private String encodeParameter(String parameterName) {
		return "_$_"+this.getParamEncoder().encodeParameterName(parameterName);
	}
	
	private ParamEncoder getParamEncoder(){
		if(StringUtils.isEmpty(this.uid)){
			this.uid = this.boClassName;
		}
		if (this.paramEncoder == null) {
			this.paramEncoder = new ParamEncoder(uid);
		}
		return this.paramEncoder;
	}
	
	private int getTotalRowCount() {
		if(request().getAttribute(Constants.DYNAMIC_QUERY_STRING) != null) 
			return -1;
		else
			return ParamUtils.getIntParameter(request(), this.encodeParameter("trc"), -1);
	}
	
	private int getStartRowIndex() {
		if(request().getAttribute(Constants.DYNAMIC_QUERY_STRING) != null) 
			return -1;
		else
			return ParamUtils.getIntParameter(request(), this.encodeParameter("st"), -1);
	}
	
	/**
	 * 初始化参数
	 * @modify wanghaifeng Dec 14, 2006 12:58:02 PM
	 * @param fetchAll	是否在查询时提取所有数据,一般仅在输出报表时才为true
	 * @throws JspException
	 */
	private void initParameters(boolean fetchAll) throws JspException {
		//初始化Meta元数据
		if(StringUtils.isEmpty(this.boClassName)){
			Meta meta = (Meta)request().getAttribute(Constants.STRUTS_META);
			if(meta != null ){
				this.boClassName = meta.getModelClass().getName();
			}
		}
		if(this.boMeta == null && !StringUtils.isEmpty(this.view)){
			this.boMeta = this.getView();
		}
		if(this.boMeta == null){
			this.boMeta = this.getMeta();
		}
		//
		this.initProperties();
		// 初始化翻页数据
		int settingPageSize = ParamUtils.getIntParameter(request(), "pageSize_" + this.getId(), -1);
		if(settingPageSize > 0) {
			this.pageSize = settingPageSize;
		}
		if (this.pageSize <= 0) this.pageSize = DEFAULT_PAGE_SIZE;
		this.totalRowCount = this.getTotalRowCount();
		int st = this.getStartRowIndex();
		if(st >= 0){
			this.start = st;
		} else {
			if(this.start < 0)
				this.start = 0;
		}
		//初始化数据源
		if(this.useStrutsForm){
			if(StringUtils.startsWith(this.dataSourceName,".")){
				this.dataSourceName = (String)request().getAttribute("STRUTS-FORMNAME")+this.dataSourceName;
			} else {
				this.dataSourceName = (String)request().getAttribute("STRUTS-FORMNAME");
			}
		}
		if(this.dataSource == null && !StringUtils.isEmpty(this.dataSourceName)){
			Object obj = pageContext.getAttribute(this.dataSourceName);
			if(obj == null){
				obj = this.request().getAttribute(this.dataSourceName);
			}
			if(obj == null){
				obj = super.session().getAttribute(this.dataSourceName);
			}
//			取属性，点号前面的为存储对象的key，点号后面的为存储的对象的数据,依次从pageContext, request, session中获取
			if(obj == null && this.dataSourceName.indexOf(".") > 0){ 
				int point = this.dataSourceName.indexOf(".");
				String dsn = this.dataSourceName.substring(0, point);
				obj = pageContext.getAttribute(dsn);
				if(obj == null){
					obj = this.request().getAttribute(dsn);
				}
				if(obj == null){
					obj = super.session().getAttribute(dsn);
				}
				if(obj != null){
					try{
						obj = BeanUtils.getProperty(obj, this.dataSourceName.substring(point + 1));
					}catch(PropertyException e){
						log.info(this, e);
					}
				}
			}
			if(obj != null){
				if(obj instanceof Collection){
					this.dataSource = new CollectionDataSource((Collection)obj);
				} else {
					this.dataSource = new SingleDataSource<DataRow>((DataRow)obj);
					this.boClassName = obj.getClass().getName();	//当对象是一个单值时设置boClassName＝对象的类型
				}
			}
		}
		if (this.dataSource != null) {
			if (this.totalRowCount == -1) {
				this.totalRowCount = this.dataSource.getTotalRowCount();
			}
			this.end = this.start + this.pageSize;
			if (this.end > this.totalRowCount) {
				this.end = this.totalRowCount;
			}
		} else {
			try {
				if(fetchAll){	//查询出所有数据
					this.dataSource = new QueryServiceDataSource(Utils.getClassByClassName(this.boClassName), this.queryString, 
							this.queryParameters, this.loadLazyData);
					this.totalRowCount = this.dataSource.getTotalRowCount();
				} else {
					this.dataSource = new QueryServiceDataSource(Utils.getClassByClassName(this.boClassName), this.queryString, 
							this.queryParameters, this.loadLazyData, this.start, this.pageSize);
					if (this.totalRowCount == -1) {
						this.totalRowCount = this.dataSource.getTotalRowCount();
					}
				}
				this.end = this.start + this.dataSource.size();
			} catch (DataSourceException e) {
				log.error(this, e);
				throw new JspException(e);
			}
		}
		this.pageNumber = (this.pageSize == 0? 0: this.start / this.pageSize) + 1;
	}

	/**
	 * 对于displayStyle=Table这个只显示一次
	 * 而对于displayStyle=Form这个在每一个记录前都显示用以区分记录之间的差异；
	 * @modify wanghaifeng Aug 25, 2006 12:44:46 PM
	 * @throws JspException
	 */
	private void outputCaption() throws JspException {
		if(StringUtils.equalsIgnoreCase(this.caption, "none") || StringUtils.equalsIgnoreCase(this.caption, "hide")) return;
		if(StringUtils.isEmpty(this.caption) && this.boMeta != null) this.caption = this.boMeta.getName();
		if(StringUtils.isEmpty(this.caption)) return;
		println("<table width='100%' border='0' align='center' cellpadding='0' cellspacing='0' class='whf_object_title' valign='middle'><tr height='25px'><td>"+ this.caption +"</td></tr></table>");
	}

	private void outputFooter() throws JspException {
		if (!StringUtils.isEmpty(this.footer)) {
			println("<div align=\"center\" width=\"100%\">");
			println(this.footer);
			println("</div>");
		}
	}

	/**
	 * 输出分页
	 * 
	 * @throws JspException
	 */
	private void outputNav() throws JspException, IOException {
		int pageCount = this.totalRowCount / this.pageSize;
		if (pageCount * this.pageSize < this.totalRowCount) {
			pageCount++;
		}
		int currentPage = this.pageNumber - 1;

		//获取当前页面uri，如果是struts则试图从request终获取struts路径，并添加?或者&结束字符串
		String pageUri = (String)request().getAttribute(Constants.STRUTS_NAVIGATION_PATH);
		if(StringUtils.isEmpty(pageUri)){
			pageUri = request().getRequestURI()+"?";
		}
		if(pageUri.indexOf("?") < 0){
			pageUri = pageUri +"?";
		}
		if(!pageUri.endsWith("&") && !pageUri.endsWith("?")){
			pageUri += "&";
		}
		//增加附加的ext信息到param中
		StringBuilder exts = new StringBuilder();
		String strc = this.encodeParameter("trc");
		String sst = this.encodeParameter("st");
		Map<String, String> keepParams = ParamUtils.getKeepParameters(request());
		for(String key: keepParams.keySet()) {
			String value = keepParams.get(key);
			if(StringUtils.isEmpty(value)) continue;
			if(key.indexOf(strc) >= 0 || key.indexOf(sst) >= 0) continue;
			exts.append(key).append("=").append(URLEncoder.encode(value, "UTF-8")).append("&");
		}
		if(exts.length() > 0){
			pageUri += "&" + exts.toString() + "&";
		}
		//
		pageUri = pageUri + this.encodeParameter("trc")+"=" + this.totalRowCount + "&"+this.encodeParameter("st")+"=";
		if (this.navStyle == NavStyle.SELECT) {
			println("<div>");
			println("<select onchange=\"javascript:window.location='" + pageUri + "'+this.value\">");
			for (int p = 0; p < pageCount; p++) {
				String selected = "";
				if (currentPage == p) {
					selected = " selected ";
				}
				int st = p * this.pageSize;
				println("\t<option value='" + st + "' " + selected + ">" + (p + 1) + "</option>");
			}
			println("</select>");
			println("</div>");
		} else if (this.navStyle == NavStyle.NUMBER) {
			println("<div>");
			for (int p = currentPage - 7; p < currentPage + 8; p++) {
				if (p <= 0) continue;
			}
			println("</div>");
		} else {
			//打印页数信息
			String navInfo = ApplicationResource.get("navigation.info", 
					new Object[]{new Integer(this.totalRowCount), new Integer(pageCount), 
					new Integer(this.start + 1), new Integer(this.end), new Integer(this.pageNumber)});
			
			int end = Math.min(start + this.pageSize, totalRowCount);
			int prev = Math.max(start - this.pageSize, 0);
			int next = Math.min(start + this.pageSize, end);
			int last = totalRowCount - totalRowCount % pageSize;
			if (totalRowCount % pageSize == 0 && totalRowCount > 0) {
				last = totalRowCount - pageSize;
				if (last < 0)
					last = start;
			}
			println("<table width='100%' border='1' align='center' cellpadding='0' cellspacing='0'><tr><td>");
			println("<table width='100%' height='26px' border='0' align='center' cellpadding='0' cellspacing='0' background='"+request().getContextPath()+"/images/filterbg.gif'></tr>");
			println("<tr><td width='53%' class='whf_table_nav_class' nowrap>" + navInfo + "</td>");
			println("<td width='47%' valign='bottom' nowrap><input type='hidden' id='pageSize_"+this.getId()+"' name='pageSize_"+this.getId()+"' style='length:10' value='"+this.pageSize+"' >");
			if (start > 0) {
				println("<a href='" + pageUri + "0'><img border='0' src='"+request().getContextPath()+"/images/pagenav_first.gif'></a>&nbsp;&nbsp;");
			} else {
				println("<img border='0' src='"+request().getContextPath()+"/images/pagenav_first_off.gif'>&nbsp;&nbsp;");
			}
			if (prev >= 0 && prev != start) {
				println("<a href='" + pageUri + prev + "'><img border='0' src='"+request().getContextPath()+"/images/pagenav_previous.gif'></a>&nbsp;&nbsp;");
			} else {
				println("<img border='0' src='"+request().getContextPath()+"/images/pagenav_previous_off.gif'>&nbsp;&nbsp;");
			}
			if (next > 0 && next < totalRowCount) {
				println("<a href='" + pageUri + next + "'><img border='0' src='"+request().getContextPath()+"/images/pagenav_next.gif'></a>&nbsp;&nbsp;");
			} else {
				println("<img border='0' src='"+request().getContextPath()+"/images/pagenav_next_off.gif'>&nbsp;&nbsp;");
			}
			if (start != last) {
				println("<a href='" + pageUri + last + "'><img border='0' src='"+request().getContextPath()+"/images/pagenav_last.gif'></a>");
			} else {
				println("<img border='0' src='"+request().getContextPath()+"/images/pagenav_last_off.gif'>");
			}
			println("</td>");
			println("<td nowrap>");
			if(this.enablePreview){
				println("<a href=\"javascript:openReportPreview('"+this.getSessionUniqueID()+"','"+this.totalRowCount+"')\">"+EXPORT_PREVIEW+"</a>");
			}
			if(this.enableExportPdf){
				println("<a href=\"javascript:openReportExport2Pdf('"+this.getSessionUniqueID()+"','"+this.totalRowCount+"')\">"+EXPORT_PDF+"</a>");
			}
			if(this.enableExportXls){
				println("<a href=\"javascript:openReportExport2Xls('"+this.getSessionUniqueID()+"','"+this.totalRowCount+"')\">"+EXPORT_XLS+"</a>");
			}
			if(this.enableCustomizeColumns && this.cacheTableAttributes){
				println("<a href=\"javascript:openCustomizeColumnsWindow('"+this.getSessionUniqueID()+"')\">"+CUSTOMIZE_COLUMNS+"</a>");
			}
			if(this.boMeta != null && this.enableDesignView) {
				if(this.boMeta instanceof View)
					println("<a href=\"javascript:openDesignViewWindow('"+ this.boMeta.getId() +"', 'view')\">"+DESIGN_VIEW+"</a>");
				else
					println("<a href=\"javascript:openDesignViewWindow('"+ this.boMeta.getId() +"', 'object')\">"+DESIGN_VIEW+"</a>");
			}
			println("</td></tr></table>");
			println("</td></tr></table>");
		}
	}

	/**
	 * @modify wanghaifeng Dec 19, 2006 5:39:26 PM
	 * @throws JspException
	 */
	private void describeEmptyTable() throws JspException {

	}
	
	/**
	 * 支持本地化策略
	 * @modify wanghaifeng Apr 18, 2007 9:00:08 AM
	 * @param col
	 * @return
	 */
	private final String getI18nDisplayTitle(final TableColumn col){
		String displayTitle = col.getDisplayTitle();
		if(displayTitle != null && displayTitle.startsWith("i18n.")){	// 使用定制的displayTitle直接本地化
			return ApplicationResource.get(displayTitle.substring(5));
		}
		// 数用数据库中定制的displayTitle
		String key = col.getProperty();
		if(!StringUtils.isEmpty(this.view)){
			key = "VIEW." + this.view + "." + col.getProperty();
		} else {
			key = this.boClassName + "." + col.getProperty();
		}
		displayTitle = ApplicationResource.get(key);
		if(displayTitle != null){
			return displayTitle;
		} else {
			return col.getDisplayTitle();
		}
	}

	/* (non-Javadoc)
	 * @see javax.servlet.jsp.tagext.Tag#doStartTag()
	 */
	public int doStartTag() throws JspException {
		if(ReportUtils.isExportMode(request())){	//选择的是导出数据
			this.initParameters(true);
			this.tableModel = new TableModel(this.dataSource, this.boMeta);
			this.tableModel.setId(getUid());
			//初始化列
			this.initColumns();
			//
			ReportUtils.setReportDescriptor(WebContextHolder.getWebContext().getUserContext(), this.tableModel);
			//
			return SKIP_BODY;
		} else {
			this.initParameters(false);
			this.tableModel = new TableModel(this.dataSource, this.boMeta);
			this.tableModel.setId(getUid());
			this.doIteration();
			return 2;
		}
	}

	protected int doIteration() {
		if (this.currentRow != null) {
			this.tableModel.addRow(this.currentRow);
			this.currentRow = null;
		}
		if (this.dataSource.hasNext()) {
			DataRow iteratedObject = this.dataSource.next();
			if (getUid() != null) {
				if ((iteratedObject != null)) {
					this.pageContext.setAttribute(getUid(), iteratedObject);
				} else {
					this.pageContext.removeAttribute(getUid());
				}
				this.pageContext.setAttribute(getUid() + ROWNUM_SUFFIX, new Integer(this.rowNumber));
			}
			this.setCurrentRow(new TableRow(iteratedObject, this.rowNumber + this.start, 
					this.wrapClass));
			this.lastIteration = !this.dataSource.hasNext();
			return 2;
		}
		this.lastIteration = true;
		return SKIP_BODY;
	}

	/* (non-Javadoc)
	 * @see javax.servlet.jsp.tagext.IterationTag#doAfterBody()
	 */
	public int doAfterBody() {
		this.rowNumber++;
		this.doAfterBodyExecuted = true;
		return doIteration();
	}

	/* (non-Javadoc)
	 * @see javax.servlet.jsp.tagext.Tag#doEndTag()
	 */
	public int doEndTag() throws JspException {
		long start = System.currentTimeMillis();
		if(ReportUtils.isExportMode(request())){	//如果时导出,不执行下面的
			return SKIP_BODY;
		}
		if (!this.doAfterBodyExecuted) {
			//在doStartTag中创建的行对象
			if (this.currentRow != null) {
				this.tableModel.addRow(this.currentRow);
			}
			//上述循环中没有循环的数据继续加入
			while (this.dataSource.hasNext()) {
				DataRow iteratedObject = this.dataSource.next();
				this.rowNumber++;
				this.setCurrentRow(new TableRow(iteratedObject, this.rowNumber + this.start, this.wrapClass));
				this.tableModel.addRow(this.currentRow);
			}
		}
		// if no rows are defined automatically get all properties from bean
		if (this.tableModel.isEmpty()) {
			describeEmptyTable();
		}
		int returnValue = EVAL_PAGE;
		try{
			this.printAssistantInfo();
			//
			this.tableModel.setCaption(this.caption);
			this.tableModel.setFooter(this.footer);
			//标签和导航
			this.outputCaption();
			if (this.navPosition == NavPosition.TOP || this.navPosition == NavPosition.BOTH) {
				this.outputNav();
			}
			//初始化列
			this.initColumns();
			//output content
			if(this.displayStyle == DisplayStyle.FORM){
				super.setClazz(this.cssClass(CSS_FORM_CLASS));
				this.writeFormHtml();
			}else if(this.displayStyle == DisplayStyle.BLOCK){
				this.writeBlockHtml();
			}else{
				super.setClazz(this.cssClass(CSS_TABLE_CLASS));
				this.writeTableHtml();
			}
			//output bottom nav and footer
			if (this.navPosition == NavPosition.BOTTOM || this.navPosition == NavPosition.BOTH) {
				this.outputNav();
			}
			this.outputFooter();
			//
		}catch(IOException e){
			log.error(this,e);
			throw new JspException(e);
		}
		release();
		super.flush();
		log.debug("TableTag output html: " + (System.currentTimeMillis() - start));
		return returnValue;
	}

	public void release() {
		super.release();
		// reset instance variables (non attributes)
		this.pageSize = DEFAULT_PAGE_SIZE;
		this.currentRow = null;
		this.doAfterBodyExecuted = false;
		this.pageNumber = 0;
		this.paramEncoder = null;
		this.rowNumber = 1;
		this.end = 0;
		this.tableModel = null;
		this.dataSource = null;
		this.dataSourceName = null;
		this.useStrutsForm = false;
		this.queryString = null;
		this.queryParameters = null;
		this.boClassName = null;
		this.boMeta = null;
		this.view = null;
		this.navPosition = NavPosition.NONE;
		this.navStyle = NavStyle.ONE_2_ONE;
		this.varTotals = null;
		this.uid = null;
		this.displayCellWidth = null;
		this.displayColumnCount = 0;
		this.pkStyle = PkStyle.COUNTER;
		this.namePrefix = "";
		this.pkSuffix = false;
		this.displayStyle = DisplayStyle.TABLE;
		this.collectionPropertyDisplay = DEFAULT_COLLECTION_PROPERTY_DISPLAY;
		this.wrapClass = null;
		this.displayWrapper = null;
		this.start = 0;
		this.rowExt = null;
		this.hideTitleRow = false;
		this.visibleLevel = 0;
		this.editableLevel = 0;
		this.cssClassSuffix = null;
		//
		this.enableExportPdf = true;
		this.enableExportXls = true;
		this.enablePreview = true;
		this.enableCustomizeColumns = true;
		this.enableDesignView = true;
		this.cacheTableAttributes = true;
		
		this.useViewProperties = false;
		
		this.caption = null;
		this.footer = null;
		
		this.rowDecorator = DEFAULT_ROW_DECORATOR;
	}

	public Map getTotals() {
		Map totalsMap = new HashMap();
//		if (this.varTotals != null) {
//			List headers = this.tableModel.getHeaders();
//			for (Iterator iterator = headers.iterator(); iterator.hasNext();) {
//				TableHeader headerCell = (TableHeader) iterator.next();
//				if (headerCell.isTotaled()) {
//					totalsMap.put(
//							"column" + (headerCell.getColumnNumber() + 1),
//							new Double(headerCell.getTotal()));
//				}
//			}
//		}
		return totalsMap;
	}
	
	/**
	 * @modify wanghaifeng Aug 22, 2006 9:58:49 PM
	 * @return
	 */
	private whf.framework.meta.entity.IMeta getMeta(){
		try{
			//在request中自动匹配
			Object viewObject = request().getAttribute(Constants.AUTO_SETTER_VIEW);
			if(viewObject != null){
				if(viewObject instanceof whf.framework.meta.entity.View){
					return (whf.framework.meta.entity.View)viewObject;
				} else if(viewObject instanceof String){
					whf.framework.meta.entity.View view = whf.framework.meta.MetaManager.getDefaultInstance().findView((String)viewObject);
					if(view != null){
						return view;
					}
				}
			}
			//使用类所对应的基本meta
			whf.framework.meta.entity.Object result = whf.framework.meta.MetaManager.getDefaultInstance().findByEntityClass(this.boClassName);
			return result;
		}catch(Exception e){
			log.info(this, e);
			return null;
		}
	}
	
	/**
	 * @modify wanghaifeng Aug 22, 2006 9:58:52 PM
	 * @return
	 */
	private whf.framework.meta.entity.View getView(){
		View view = whf.framework.meta.MetaManager.getDefaultInstance().findView(this.view);
		if(view != null && view.getReferencedObject() != null && StringUtils.isEmpty(this.boClassName)){
			this.boClassName = view.getReferencedObject().getBoClassName();
		}
		return view;
	}
	
	private void writeRowBlockHtml(TableRow row) throws JspException, IOException{
		if(this.displayWrapper != null && !(displayWrapper instanceof IDisplayAppendWrapper)){
			displayWrapper.writeHtml(this.pageContext, this.tableModel.getColumns(), row);
		} else {
			List<TableColumn> columns = this.tableModel.getColumns();
			println("<table class='"+this.cssClass(CSS_BLOCK_CLASS)+"'>");
			println("<tr class='"+this.cssClass(CSS_BLOCK_HEADER_ROW)+"' valign='top'>").print("<td colspan='2' class='"+this.cssClass(CSS_BLOCK_HEADER_CELL)+"'>");
			DataRow rowObject = row.getRowObject();
			boolean isEmpty = StringUtils.isEmpty(rowObject.getDisplayName());	//使用名称判定该对象是否是一个空对象
			if (this.pkStyle != PkStyle.HIDDEN) {
				println("<table border='0' cellspacing='0' cellpadding='0' width='100%'><tr>");
				Map<String, String> pkClass = Utils.newHashMap();	//主键风格
				pkClass.put("class", this.cssClass(CSS_TABLE_BODY_CELL));
				pkClass.put("width", "1%");
				String idName = this.namePrefix + Constants.ID;
				String idId = this.namePrefix+rowObject.getPrimaryKey();
				if(!isEmpty){
					String disabled = "";
					if(!PermissionUtils.hasPermission(WebContextHolder.getWebContext(super.request()).getUserContext(), rowObject)){
						disabled = DISABLED;
					}
					if (this.pkStyle == PkStyle.RADIO) {
						printTd("<input type='radio' "+disabled+" name='"+idName+"' id='" + idId + "' value='" + rowObject.getPrimaryKey() + "' >", pkClass);
					} else if (this.pkStyle == PkStyle.CHECKBOX) {
						printTd("<input type='checkbox' "+disabled+" name='"+idName+"' id='" + idId + "' value='" + rowObject.getPrimaryKey() + "' >", pkClass);
					} else {
						printTd(String.valueOf(row.getNumber()), pkClass);
					}
				} else {
					printTd("");
				}
				printTd(rowObject.getDisplayName());
				println("</tr></table>");
			} else {
				println(rowObject.getDisplayName());
			}
			println("</td>").print("</tr>");
			//
			for(TableColumn col: columns){
				if("name".equalsIgnoreCase(col.getProperty())) continue;
				String content = this.getCellContent(row, col);
				String displayTitle = this.getI18nDisplayTitle(col);
				if(isEmpty){
					content = "";
					displayTitle = "";
				}
				if(StringUtils.isEmpty(content) || StringUtils.equalsIgnoreCase(content, "&nbsp;")) continue;
				println("<tr valign='top'>");
				if(StringUtils.equalsIgnoreCase(displayTitle, col.getProperty()) || StringUtils.equalsIgnoreCase(displayTitle, "&nbsp;")){
					println("<td colspan='2' class='"+this.cssClass(CSS_BLOCK_BODY_CELL)+"'>").print(content).print("</td>");
				} else {
					println("<td width='30%' class='"+CSS_BLOCK_BODY_ROW+"'>").print(displayTitle).print("</td>");
					println("<td width='*' class='"+this.cssClass(CSS_BLOCK_BODY_CELL)+"'>").print(content).print("</td>");
				}
				println("</tr>");
			}
			println("</table>");
			if(this.displayWrapper != null && (displayWrapper instanceof IDisplayAppendWrapper)){	//补充输出
				displayWrapper.writeHtml(pageContext, this.tableModel.getColumns(), row);
			}
		}
	}
	
	/**
	 * @modify wanghaifeng Aug 24, 2006 11:01:01 PM
	 * @return 用于定制列，在定制列中唯一存储在session中的key
	 */
	private String getSessionUniqueID(){
		String url = (String)request().getAttribute(Constants.STRUTS_PATH);
		if(url == null) url = request().getRequestURI();
		StringBuilder id = new StringBuilder(url).append("?");
		if(!StringUtils.isEmpty(this.view))
			id.append(this.view);
		else
			id.append(this.boClassName);
		return id.toString();
	}
	
	/**
	 * @modify wanghaifeng Aug 24, 2006 11:01:46 PM
	 * @return 保存当前存储查询对象的key
	 */
	private String getQueryDataSourceID(){
		StringBuilder id = new StringBuilder("QUERYFORM:"+this.boClassName);
		if(this.boMeta != null){
			id.append(this.boMeta.getClass().getSimpleName()).append(this.boMeta.getId());
		}
		id.append(this.getId());
		//缓存最近查询使用key供struts中使用
		session().setAttribute(Constants.SESSION_LASTEST_QUERY_KEY, id.toString());
		return id.toString();
	}
	
	/**
	 * 初始化列信息
	 * 按照一下步骤：
	 * 首先检查session中是否有对应的列，如果有使用，主要发生在定制列后保存到session的数据，不保存到数据库 
	 * 如果上述没有着从对应的meta数据中获取
	 * 如果上述还是没有则从对象描述中获取
	 * 最后列数据保存到session中；
	 * @throws JspException
	 */
	private void initColumns() throws JspException{
		UserContext uc = WebContextHolder.getWebContext(request()).getUserContext();
		List<TableColumn> cols = null;
		TableAttributes tableAttributes = null;
		String uid = this.getSessionUniqueID();
		// 取缓存信息
		if(!this.tableModel.getColumns().isEmpty()) {	//设置的列具有最高优先级
			cols = this.tableModel.getColumns();
		} else {
			if(uc != null) {
				tableAttributes = uc.getTableAttributes(uid);
				if(tableAttributes != null){
					cols = tableAttributes.getTableColumns();
				}
			}
			// 缓存中不存在，直接从元数据中获取
			if(cols == null || !this.cacheTableAttributes) {
				cols = Utils.newArrayList();
				assert this.boMeta != null: "expect get column info from meta, but meta is null!";
				List<IProperty> properties = this.boMeta.getSortedProperties();
				for(IProperty prop: properties){
					if(prop.isVisible() && this.visibleLevel >= prop.getVisibleLevel()){
						cols.add(new TableColumn(prop));
					}
				}
			}
		}
		// cache table attributes
		TableAttributes attributes = new TableAttributes(cols, this.pageSize, this.queryString);
		uc.setTableAttributes(uid, attributes);
		// set tableModel's columns
		this.tableModel.setColumns(cols);
	}
	
	/**
	 * 从View中获取定制属性
	 */
	private void initProperties(){
		UserContext uc = WebContextHolder.getWebContext(request()).getUserContext();
		TableAttributes attributes = null;
		if(uc != null){
			attributes = uc.getTableAttributes(this.getSessionUniqueID());
		}
		// initialize properties from view
		if(this.boMeta != null && this.boMeta instanceof View && this.useViewProperties) {
			View view = (View)this.boMeta;
			if(StringUtils.isEmpty(this.caption))
				this.setCaption(view.getTitle());
			this.displayColumnCount = view.getDisplayColumnCount();
			this.visibleLevel = view.getVisibleLevel();
			this.editableLevel = view.getEditableLevel();
			this.editable = view.isEditable();
			this.pageSize = view.getPageSize();
			this.setPkStyle(view.getPkStyle());
			this.pkSuffix = view.isPkSuffix();
			this.setDisplayStyle(view.getDisplayStyle());
			this.setNavPosition(view.getNavPosition());
		} else if(attributes != null && attributes.getPageSize() > 0) {
			this.pageSize = attributes.getPageSize();
		}
		// 是第一次加载，如果是视图将queryString并入传入的queryString中
		if(ReportUtils.isExportMode(request())) {
			this.queryString = attributes.getQueryString();
		} else {
			String dynamicQueryString = (String)request().getAttribute(Constants.DYNAMIC_QUERY_STRING);
			if(this.getTotalRowCount() == -1 || !StringUtils.isEmpty(dynamicQueryString)) {
				if(this.boMeta != null && this.boMeta instanceof View) {
					View view = (View)this.boMeta;
					String qs = view.getQueryString();
					this.addDynamicQueryString(qs);
				}
				this.addDynamicQueryString(dynamicQueryString);
				if(attributes != null)
					attributes.setQueryString(this.queryString);
			} else {
				this.queryString = attributes.getQueryString();
			}
		}
	}
	
	private void addDynamicQueryString(String dqs) {
		if(StringUtils.isEmpty(dqs)) return;
		if(StringUtils.isEmpty(this.queryString)) {
			this.queryString = dqs;
			return;
		}
		int orderPoint = this.queryString.toLowerCase().lastIndexOf("order ");
		int byPoint = this.queryString.toLowerCase().trim().indexOf("by ", orderPoint);
		String orderStmt = "";
		String preStmt = this.queryString;
		if(orderPoint >= 0 && byPoint >= 0) {
			orderStmt = this.queryString.substring(orderPoint);
			preStmt = this.queryString.substring(0, orderPoint);
		}
		if(!StringUtils.isEmpty(preStmt)) preStmt += " and ";
		this.queryString = preStmt + " (" + dqs + ")" + orderStmt; 
	}
	
	private void writeBlockHtml() throws JspException, IOException{
		int width = 100 / (displayColumnCount == 0?1:displayColumnCount);
		println("<table class='"+this.cssClass(CSS_BLOCK_TABLE_CLASS)+"'>");
		println("<tbody>");
		for (Iterator<TableRow> rows = this.tableModel.getRows().iterator(); rows.hasNext();) {
			TableRow row = rows.next();
			println("<tr>");
			println("<td width'"+width+"%' valign='top'>");
			this.writeRowBlockHtml(row);
			println("</td>");
			int recCount = 1;
			while(recCount < this.displayColumnCount && rows.hasNext()){
				row = rows.next();
				println("<td width'"+width+"%' valign='top'>");
				this.writeRowBlockHtml(row);
				println("</td>");
				recCount ++;
			}
			println("<tr>");
		}
		println("</tbody>");
		println("</table>");
	}
	
	/**
	 * 获取单元格内容
	 * @param row 行对象，可以通过getCurrentRow()方法获取；
	 * @param col 列对象
	 * @return
	 */
	private String getCellContent(TableRow row, TableColumn col){
		DataRow rowObject = row == null? null: row.getRowObject();
		Object propertyValue = row == null? null: row.getPropertyValue(col.getProperty());
		String displayValue = "";
		if(this.editable && !col.isReadonly() && this.editableLevel >= col.getEditableLevel()){
			displayValue = this.getEditableCell(col, propertyValue, rowObject);
		} else if(col.isOutputDirect()){	//直接输出列值
			displayValue = propertyValue == null?"&nbsp;":propertyValue.toString();
		} else {
			PropertyTypeEnum propertyType = col.getPropType(propertyValue);
			String metaDataType = col.getDataType();
			Class metaDataClass = Utils.getClassByClassName(metaDataType);
			if(propertyValue != null){
				if(propertyType == PropertyTypeEnum.COLLECTION){
					if(this.collectionPropertyDisplay != null){
						displayValue = this.collectionPropertyDisplay.display(row.getRowObject(), (Collection)propertyValue);
					}
				} else if(propertyType == PropertyTypeEnum.ENTITY){
					displayValue = ((DataRow)propertyValue).getDisplayName();
				} else if(propertyType == PropertyTypeEnum.BOOLEAN){
					if(propertyValue instanceof Boolean){
						Boolean b = (Boolean)propertyValue;
						if(b){
							displayValue = BOOLEAN_TRUE;
						} else {
							displayValue = BOOLEAN_FALSE;
						}
					} else { //当其他类型要求显示为boolean类型时，多指String类型
						String b = propertyValue.toString();
						if(StringUtils.equalsIgnoreCase(b.trim(),"true")){
							displayValue = BOOLEAN_TRUE;
						} else {
							displayValue = BOOLEAN_FALSE;
						}
					}
				} else if(propertyType == PropertyTypeEnum.DATETIME){		//对于时间日期型，display定义日期显示格式
					String displayType = col.getDisplayType();
					if(StringUtils.isEmpty(displayType)){
						displayValue = DateUtils.formatDate((java.util.Date)propertyValue);
					} else {
						String dateFormat = ApplicationResource.get(WebContextHolder.getWebContext(super.request()).getUserContext().getLocale(), "common." + displayType.toLowerCase());
						if(dateFormat == null) dateFormat = "yyyy-MM-dd";
						displayValue = DateUtils.formatDate((java.util.Date)propertyValue, dateFormat);
					}
				} else if(propertyType == PropertyTypeEnum.DICT){
					String dictItemCode = propertyValue.toString();
					if(!StringUtils.isEmpty(dictItemCode)){
						DictManager dm = DictManagerImp.getDictManager();
						String displayType = col.getDisplayType();
						if("MULTIPLE".equalsIgnoreCase(displayType)){
							String[] codes = StringUtils.split(propertyValue.toString(), ",");
							for(String code: codes){
								if(code == null) continue;
								DictItem dictItem = dm.findDictItem(col.getDictCategoryCode(), code);
								if(dictItem == null) continue;
								displayValue += dictItem.getDisplayName() +",";
							}
						} else {
							DictItem dictItem = dm.findDictItem(col.getDictCategoryCode(), propertyValue.toString());
							displayValue = dictItem == null? "": dictItem.getDisplayName();
						}
					} else {
						displayValue = "";
					}
				} else if(propertyType == PropertyTypeEnum.IMAGEFILE){
					String path = propertyValue == null?"":Base64.encode(propertyValue.toString());
					displayValue = "<img src='"+request().getContextPath()+"/Download?path="+path+"' border='0'>";
				} else if(propertyType == PropertyTypeEnum.FILE){
					String path = propertyValue == null?"":Base64.encode(propertyValue.toString());
					displayValue = "<a href='"+request().getContextPath()+"/Download?path="+path+"' border='0'>"+DOWNLOAD+"</a>";
				} else if(metaDataClass != null && TypeUtils.isImplementsFrom(metaDataClass, Entity.class)){ //当字符串绑定到对象时的显示方法
					Meta meta = MetaFactory.findByModelClass(metaDataClass);
					if(meta != null){
						try{
							Serializable pk = Long.parseLong(propertyValue.toString());
							Service service = BeanFactory.getService(meta);
							Entity entity = service.findByPrimaryKey(pk);
							displayValue = entity.getDisplayName();
						}catch(Exception e){
							displayValue = "";
						}
					}
				} else {
					if(propertyValue.getClass() == double.class || propertyValue.getClass() == Double.class){
						displayValue = StringUtils.format(((Double)propertyValue).doubleValue(), col.getFormat());
					} else if(propertyValue.getClass() == float.class || propertyValue.getClass() == Float.class){
						displayValue = StringUtils.format(((Float)propertyValue).doubleValue(), col.getFormat());
					} else {
						displayValue = propertyValue.toString();
					}
					if(col.getMaxLength() > 0 && displayValue != null && displayValue.length() > col.getMaxLength()){ //spit the value
						displayValue = displayValue.substring(0, col.getMaxLength());
					}
				}
			}
			if(StringUtils.isEmpty(displayValue)) displayValue = "&nbsp;";
			if(!StringUtils.isEmpty(col.getHyperLink())){
				String link = BeanUtils.replaceByProperty(row.getRowObject(), col.getHyperLink());
				link = StringUtils.wrapHyperLink(request(), link);
				displayValue = "<a href='"+link+"'>"+displayValue+"</a>";
			} else if(!StringUtils.isEmpty(col.getJsFunction())){
				String jsFunc = BeanUtils.replaceByProperty(row.getRowObject(), col.getJsFunction());
				displayValue = "<a href=\"javascript:"+jsFunc+"\">"+displayValue+"</a>";
			}
			if(col.isViewObjectDetail() && col.getPropertyObject() != null ){ //打开查看详细资料窗口
				String viewDetail = "<a href='#' onclick=\"openDetailWindow('"+col.getDataType()+"','"+row.getRowObject().getPrimaryKey()+"','"+col.getPropertyObject().getId()+"')\"><img src='"+request().getContextPath()+"/images/detail.gif'></a>";
				displayValue += viewDetail;
			}
		}
		return displayValue;
	}
	
	private void writeFormHtml() throws JspException, IOException{
		int count = 0;
		Map<String, String> formLabelClass = Utils.newHashMap();	//表单标题显示风格
		formLabelClass.put("class",this.cssClass(CSS_FORM_LABEL_CLASS));
		if(this.displayColumnCount == 0) this.displayColumnCount = 1;
		if(StringUtils.isEmpty(this.displayCellWidth)){
			String displayTitleWidth = String.valueOf(150 / (this.displayColumnCount * 5)) + "%";
			this.displayCellWidth = String.valueOf(350 / (this.displayColumnCount * 5)) + "%";
			formLabelClass.put("width", displayTitleWidth);
		}
		for (Iterator<TableRow> rows = this.tableModel.getRows().iterator(); rows.hasNext();) {
			if(count > 0){
				this.outputCaption();
			}
			count ++;
			println(super.getOpenTag("table"));
			println("<tbody>");
			TableRow row = rows.next();
			//
			boolean includeIdColumn = false;
			for (Iterator<TableColumn> columnIterator = this.tableModel.getColumns().iterator(); columnIterator.hasNext();) {
				println("<tr class='"+this.cssClass(CSS_FORM_ROW_CLASS)+"'>");
				TableColumn col = columnIterator.next();
				int colspan = col.getColspan();
				if(colspan <= 1) colspan = 1;
				if(StringUtils.equals(Constants.ID, col.getProperty())){
					includeIdColumn = true;
				}
				String requiredText = "";
				if(col.isRequired() && this.editable){
					requiredText = "<font color='red'>*</font>";
				} else {
					requiredText = "";
				}
				String displayValue = this.getCellContent(row, col);
				formLabelClass.put("id","title_"+col.getProperty()); //使用javascript验证时可以找到property对应的title
				super.printTd(this.getI18nDisplayTitle(col)+requiredText, formLabelClass);
				print("<td width='"+this.displayCellWidth+"' class='"+this.cssClass(CSS_FORM_CONTENT_CLASS)+"' colspan='"+colspan+"'>").print(displayValue).print("</td>");
				//
				int columnCount = colspan;
				while(columnCount < this.displayColumnCount){
					if(columnIterator.hasNext()){
						col = columnIterator.next();
						colspan = col.getColspan();
						if(colspan > this.displayColumnCount - columnCount ){
							colspan = this.displayColumnCount - columnCount;
						}
						if(colspan <= 1) colspan = 1;
						if(StringUtils.equals(Constants.ID, col.getProperty())){
							includeIdColumn = true;
						}
						if(col.isRequired() && this.editable){
							requiredText = "<font color='red'>*</font>";
						} else {
							requiredText = "";
						}
						displayValue = this.getCellContent(row, col);
						formLabelClass.put("id","title_"+col.getProperty());
						super.printTd(this.getI18nDisplayTitle(col) + requiredText, formLabelClass);
						print("<td width='"+this.displayCellWidth+"' class='" + this.cssClass(CSS_FORM_CONTENT_CLASS)+"'>").print(displayValue).print("</td>");
						columnCount += colspan;
					} else {
						super.printTd("&nbsp;", formLabelClass);
						print("<td width='"+this.displayCellWidth+"' class='" + this.cssClass(CSS_FORM_CONTENT_CLASS)+"'>").print("&nbsp;").print("</td>");
						columnCount++;
					}
				}
				//
				println("</tr>");
			}
			if(!includeIdColumn){
				DataRow bo = row.getRowObject();
				String name = this.namePrefix+Constants.ID+(this.pkSuffix? String.valueOf(bo.getPrimaryKey()): "");
				super.printInput("hidden", name, String.valueOf(bo.getPrimaryKey()));
			}
			println("</tbody>");
			println("</table>");
		}
	}

	private void writeTableHtml() throws JspException, IOException {
		List<TableColumn> columns = this.tableModel.getColumns();
		super.setBorder("1");
		super.setCellpadding("0");
		super.setCellspacing("0");
		super.setAttribute("pkStyle", this.pkStyle == null? "": this.pkStyle.toString());
		super.setAttribute("firstRowIndex", this.tableModel.getRows().size() ==0 ? "-1":String.valueOf(this.tableModel.getRows().get(0).getNumber()));
		super.setAttribute("lastRowIndex", this.tableModel.getRows().size() ==0 ? "-1":String.valueOf(this.tableModel.getRows().get(this.tableModel.getRows().size() - 1).getNumber()));
		super.setAttribute("boClassName", this.boClassName);
		super.setAttribute("metaId", this.boMeta != null? String.valueOf(this.boMeta.getId()): "");
		super.setAttribute("metaCode", this.boMeta != null? this.boMeta.getCode(): "");
		println(super.getOpenTag("table"));
		if(!this.hideTitleRow){	//打印列表标题头
			println("<thead>").print("<tr  class='"+this.cssClass(CSS_TABLE_HEADER_ROW)+"'>");
			if(this.pkStyle == PkStyle.CHECKBOX || this.pkStyle == PkStyle.CHECKBOX_AND_COUNTER) {
				println("<td class='"+this.cssClass(CSS_TABLE_PK_CONTROL)+"' width='1%'><input type='checkbox' onclick='changeAllSelections(this)' /></td>");
			}
			if (this.pkStyle == PkStyle.RADIO || this.pkStyle == PkStyle.RADIO_AND_COUNTER) {
				println("<td class='"+this.cssClass(CSS_TABLE_PK_CONTROL)+"' width='1%'>&nbsp;</td>");
			}
			if(this.pkStyle == PkStyle.COUNTER || this.pkStyle == PkStyle.CHECKBOX_AND_COUNTER || this.pkStyle == PkStyle.RADIO_AND_COUNTER) {
				println("<td class='"+this.cssClass(CSS_TABLE_COUNTER)+"' width='1%'>#</td>");
			}
			for (int i = 0; i < columns.size(); i++) {
				TableColumn col = columns.get(i);
				if(!col.isVisible()) continue;
				String label = this.getI18nDisplayTitle(col);
				println("<td class='"+this.cssClass(CSS_TABLE_HEADER_CELL)+"' nowrap>").print(label).print("</td>");
			}
			if(!StringUtils.isEmpty(this.rowExt)){
				println("<td class='"+this.cssClass(CSS_TABLE_HEADER_CELL)+"'>&nbsp;</td>");
			}
			println("</tr>").print("</thead>");
		}
		println("<tbody>");
		//定义显示风格
		Map<String, String> tableClass = Utils.newHashMap();
		tableClass.put("class", this.cssClass(CSS_TABLE_BODY_CELL));
		Map<String, String> pkClass = Utils.newHashMap();	//主键风格
		pkClass.put("class", this.cssClass(CSS_TABLE_BODY_CELL));
		pkClass.put("width", "1%");
		//
		UserContext userContext = WebContextHolder.getWebContext(super.request()).getUserContext();
		//显示数据
		String css1 = this.cssClass(CSS_TABLE_BODY_ROW1);
		String css2 = this.cssClass(CSS_TABLE_BODY_ROW2);
		for (Iterator<TableRow> rows = this.tableModel.getRows().iterator(); rows.hasNext();) {
			TableRow row = rows.next();
			DataRow rowObject = row.getRowObject();
			String css = css2;
			if(row.getNumber() % 2 == 1) css = css1;

			String extRowProperty = "";
			if(this.rowDecorator != null) 
				extRowProperty = this.rowDecorator.decorateRow(row, Utils.toMap(new String[]{"css"}, new String[]{css}));
			println("<tr ").print(extRowProperty).print(">");
			//write id with the display style
			if (this.pkStyle != PkStyle.HIDDEN) {
				String idName = this.namePrefix + Constants.ID;
				String idId = this.namePrefix+rowObject.getPrimaryKey();
				String disabled = "";
				if(!PermissionUtils.hasPermission(userContext, rowObject)){
					disabled = DISABLED;
				}
				if(this.pkStyle == PkStyle.CHECKBOX || this.pkStyle == PkStyle.CHECKBOX_AND_COUNTER) {
					println("<td class='"+this.cssClass(CSS_TABLE_PK_CONTROL)+"' width='1%'><input type='checkbox' "+disabled+" name='"+idName+"' id='" + idId + "' value='" + rowObject.getPrimaryKey() + "' ></td>");
				}
				if (this.pkStyle == PkStyle.RADIO || this.pkStyle == PkStyle.RADIO_AND_COUNTER) {
					println("<td class='"+this.cssClass(CSS_TABLE_PK_CONTROL)+"' width='1%'><input type='radio' "+disabled+" name='"+idName+"' id='" + idId + "' value='" + rowObject.getPrimaryKey() + "' ></td>");
				}
				if(this.pkStyle == PkStyle.COUNTER || this.pkStyle == PkStyle.CHECKBOX_AND_COUNTER || this.pkStyle == PkStyle.RADIO_AND_COUNTER) {
					println("<td class='"+this.cssClass(CSS_TABLE_COUNTER)+"' width='1%'>" + String.valueOf(row.getNumber()) + "</td>");
				}
			}
			for (Iterator<TableColumn> columnIterator = this.tableModel.getColumns().iterator(); columnIterator.hasNext();) {
				TableColumn col = columnIterator.next();
				if(!col.isVisible()) continue;
				String displayValue = this.getCellContent(row, col);
				printTd(displayValue, tableClass);
			}
			if(!StringUtils.isEmpty(this.rowExt)){
				String displayValue=BeanUtils.replaceByProperty(row.getRowObject(), row.getRowExt());
				Map<String, String> map = Utils.newHashMap();
				map.putAll(tableClass);
				map.put("width", "1");
				printTd(displayValue, map);
			}
			println("</tr>");
		}
		println("</tbody>");
		println("</table>");
	}
	
	private void printAssistantInfo() throws JspException{
		super.printInput("hidden", Constants.BO_CLASS_NAME, this.boClassName);
		super.printInput("hidden", this.boClassName+Constants.UID, this.getUid());
		super.printInput("hidden", this.boClassName+Constants.START, String.valueOf(this.start));	//导航
		super.printInput("hidden", this.boClassName+Constants.END, String.valueOf(this.end));
		super.printInput("hidden", this.boClassName+Constants.PK_SUFFIX, String.valueOf(this.pkSuffix));	//pk是否作为名称后缀
		super.printInput("hidden", this.boClassName+Constants.NAME_PREFIX, this.namePrefix);				//名称前缀
		super.printInput("hidden", this.boClassName+Constants.DATASOURCE_NAME, this.dataSourceName);		//数据源名称
		super.printInput("hidden", this.boClassName+Constants.META, String.valueOf(this.boMeta == null?0:this.boMeta.getId()));	//保存类的元数据供后期合法性校验
		if(this.boMeta != null && this.boMeta instanceof View){
			super.printInput("hidden", this.boClassName+Constants.META_TYPE, "View");
			super.printInput("hidden", this.boClassName+Constants.META_VIEW, ((View)boMeta).getCode());
		} else {
			super.printInput("hidden", this.boClassName+Constants.META_TYPE, "Object");
		}
	}
	
	/**
	 * 枚举选择对象
	 * @modify wanghaifeng Sep 4, 2006 10:20:33 PM
	 * @param dataTypeClass
	 * @param queryString
	 * @param rowObject
	 * @return
	 */
	private List<Entity> enumSelections(Class<? extends Entity> dataTypeClass,String queryString, DataRow rowObject){
		try{
			Service service = BeanFactory.getService(MetaFactory.findByModelClass(dataTypeClass));
			queryString = BeanUtils.replaceByProperty(rowObject, queryString);		//使用对象属性值替换查询字符串中的值
			return service.find(queryString);
		}catch(Exception e){
			log.warn(this, e);
			return null;
		}
	}
	
	/**
	 * @modify wanghaifeng Sep 4, 2006 10:21:06 PM
	 * @param col	当前列
	 * @param propertyValue 当前列对应的属性的值
	 * @param rowObject 当前行对应的业务对象
	 * @return 字符串对象，一般是一个html控件字符串
	 * 其中，propertyValue = rowObject.get(col.getProperty);
	 */
	private String getEditableCell(TableColumn col, Object propertyValue, DataRow rowObject){
		Serializable primaryKey = rowObject == null? "-1": rowObject.getPrimaryKey();
		String name = this.namePrefix+col.getProperty()+(this.pkSuffix? String.valueOf(primaryKey): "");
		String id = name+"_"+primaryKey;
		String dataType = col.getDataType();
		if(StringUtils.isEmpty(dataType))  dataType = "String";
		String queryString = BeanUtils.replaceByProperty(rowObject, col.getQueryString());
		
		//判定业务对象类型
		PropertyTypeEnum propertyTypeInfo = col.getPropType(propertyValue);
		String displayType = col.getDisplayType();
		Class dataTypeClass = Utils.getClassByClassName(dataType);
		//
		AttributeStringBuilder res = new AttributeStringBuilder(false);
		if(propertyTypeInfo == PropertyTypeEnum.ENTITY){
			name = name+Constants.BOID_SUFFIX;
			Entity entity = null;
			if(propertyValue != null){
				if(propertyValue instanceof Entity){
					entity = (Entity)propertyValue;
				} else { //字符串绑定对象
					try{
						long valId = Long.parseLong(propertyValue.toString());
						Meta meta = MetaFactory.findByModelClass(dataTypeClass);
						Service service = BeanFactory.getService(meta);
						entity = service.findByPrimaryKey(valId);
					}catch(Exception ignore){
						//
					}
				}
			}
			String dispValue = (entity == null? "": entity.getDisplayName());
			if(col.isEnumSelections()){//枚举选项
				long selectionId = entity == null?0:entity.getId();
				List<Entity> list = enumSelections(dataTypeClass, queryString, rowObject);
				if(list != null){
					for(Entity b:list){
						String checked = selectionId == b.getId()?"checked":"";
						res.append("<span nowrap>");
						res.append("<input type='radio'").append("name", name).append("id", id).append("value", b.getId()).append(checked);
						if(!StringUtils.isEmpty(col.getHtmlAttributes())) res.append(col.getHtmlAttributes());
						res.append(">");
						res.append(b.getDisplayName());
						res.append("</span>");
					}
				}
			} else {
				res.append("<span nowrap>");
				res.append("<input type='text' class='selectInput' ").append("value", dispValue);
				res.append("name","_"+name).append("id","_"+id).append("dataType", dataType).append("kind", dataType);
				res.append("queryString", queryString).append("required", col.isRequired());
				res.append("dictCategory", col.getDictCategoryCode());
				res.append("multiselect",false).append("size", col.getDisplayWidth());
				if(!StringUtils.isEmpty(col.getPromptTitle())) res.append("title", col.getPromptTitle());
				if(!StringUtils.isEmpty(col.getHtmlAttributes())) res.append(col.getHtmlAttributes());
				res.append(">");
				
	//			对于Meta中定义的属性可以提供查询
				if(col.isQueryCondition() && col.getPropertyObject() != null){ //使用搜索按钮
					res.append("<img ").append("style","cursor:hand").append("src",request().getContextPath()+"/images/search.gif").append("onclick","openSearchWindow('"+id+"','"+col.getPropertyObject().getId()+"','false')").append(">");
				} else {
					res.append("<img ").append("src",request().getContextPath()+"/images/search.gif").append(">");
				}
				res.append("</span>");
				
				res.append("<input type='hidden' ");
				res.append("name", name).append("id", id).append("value", (entity == null? 0: entity.getId()));
				res.append(">");
			}
		} else if(propertyTypeInfo == PropertyTypeEnum.COLLECTION){
			Collection coll = (Collection)propertyValue;
			StringBuilder dispNames = new StringBuilder(coll.size() * 6);
			StringBuilder dispValues = new StringBuilder(coll.size() * 2);
			for(Object obj: coll){
				if(obj == null) continue;
				Entity entity = (Entity)obj;
				dispNames.append(entity.getDisplayName()).append(",");
				dispValues.append(entity.getId()).append(",");
			}
			//
			name = name+Constants.BOID_SUFFIX;
			if(col.isEnumSelections()){	//枚举对象
				List<Entity> list = enumSelections(dataTypeClass, queryString, rowObject);
				if(list != null){
					for(Entity b:list){
						String checked = "";
						for(Object obj: coll){
							if(obj == null) continue;
							Entity entity = (Entity)obj;
							if(entity.getId() == b.getId()){
								checked = "checked";
							}
						}
						res.append("<span nowrap>");
						//枚举项通过响应其onclick事件与hidden域中的值同步
						res.append("<input type='checkbox'").append("name", name+"_Option").
							append("onclick","enumOptionSelect('"+name+"','"+id+"')").append("value", b.getId()).append(checked);
						if(!StringUtils.isEmpty(col.getHtmlAttributes())) res.append(col.getHtmlAttributes());
						res.append(">");
						res.append(b.getDisplayName());
						res.append("</span>");
					}
				}
			} else {
				res.append("<span nowrap>");
				res.append("<input type='text' class='selectInput' ").append("value", dispNames.toString());
				res.append("name","_"+name).append("id","_"+id).append("dataType", dataType).append("kind", dataType);
				res.append("queryString", queryString).append("required", col.isRequired());
				res.append("dictCategory", col.getDictCategoryCode());
				res.append("multiselect",true).append("size", col.getDisplayWidth());
				if(!StringUtils.isEmpty(col.getPromptTitle())) res.append("title", col.getPromptTitle());
				if(!StringUtils.isEmpty(col.getHtmlAttributes())) res.append(col.getHtmlAttributes());
				res.append(">");
				
	//			对于Meta中定义的属性可以提供查询
				if(col.isQueryCondition() && col.getPropertyObject() != null){ //使用搜索按钮
					res.append("<img ").append("style","cursor:hand").append("src",request().getContextPath()+"/images/search.gif").append("onclick","openSearchWindow('"+id+"','"+col.getPropertyObject().getId()+"','false')").append(">");
				} else {
					res.append("<img ").append("src",request().getContextPath()+"/images/search.gif").append(">");
				}
				res.append("</span>");
			}
			res.append("<input type='hidden' ");
			res.append("name", name).append("id", id).append("value", dispValues.toString());
			res.append(">");
		} else if(propertyTypeInfo == PropertyTypeEnum.DICT){//对于字典项，除了显示与BO区别，其它一样
			String dispValue = "";
			if(propertyValue != null && !StringUtils.isEmpty(propertyValue.toString())){
				DictManager dm = DictManagerImp.getDictManager();
				if("MULTIPLE".equalsIgnoreCase(displayType)){
					String[] codes = StringUtils.split(propertyValue.toString(), ",");
					for(String code: codes){
						if(code == null) continue;
						DictItem dictItem = dm.findDictItem(col.getDictCategoryCode(), code);
						if(dictItem == null) continue;
						dispValue += dictItem.getDisplayName() +",";
					}
				} else {
					DictItem dictItem = dm.findDictItem(col.getDictCategoryCode(), propertyValue.toString());
					dispValue = dictItem == null? "": dictItem.getDisplayName();
				}
			}
			dataType = DictItem.class.getName();
			res.append("<span nowrap>");
			res.append("<input type='text' class='selectInput' ").append("value", dispValue);
			res.append("name","_"+name).append("id","_"+id).append("dataType", dataType).append("maxLength", "").append("kind", dataType);
			res.append("queryString", queryString).append("required", col.isRequired());
			res.append("dictCategory", col.getDictCategoryCode());
			res.append("multiselect", "multiple".equalsIgnoreCase(col.getDisplayType())).append("size", col.getDisplayWidth());
			if(!StringUtils.isEmpty(col.getPromptTitle())) res.append("title", col.getPromptTitle());
			if(!StringUtils.isEmpty(col.getHtmlAttributes())) res.append(col.getHtmlAttributes());
			res.append(">");
			
			res.append("<img ").append("src",request().getContextPath()+"/images/select.gif").append(">").append("</span>");
			
			res.append("<input type='hidden' ");
			res.append("name", name).append("id", id).append("value", propertyValue == null?"":propertyValue.toString());
			res.append(">");
		} else if(propertyTypeInfo == PropertyTypeEnum.SYSTEM){
			String dispValue = (propertyValue== null? "": propertyValue.toString());
			if(StringUtils.equalsIgnoreCase(dispValue, "&nbsp;")){
				dispValue = "";
			}
			res.append("<span nowrap>");
			res.append("<input type='text' class='selectInput' ").append("value", dispValue);
			res.append("name","_"+name).append("id","_"+id).append("dataType", dataType).append("kind", dataType);
			res.append("queryString", queryString).append("required", col.isRequired());
			res.append("dictCategory", dataType);
			res.append("multiselect",false).append("size", col.getDisplayWidth());
			if(!StringUtils.isEmpty(col.getPromptTitle())) res.append("title", col.getPromptTitle());
			res.append(">");
			
//			对于Meta中定义的属性可以提供查询
			if(col.isQueryCondition() && col.getPropertyObject() != null){ //使用搜索按钮
				res.append("<img ").append("style","cursor:hand").append("src",request().getContextPath()+"/images/search.gif").append("onclick","openSearchWindow('"+id+"','"+col.getPropertyObject().getId()+"','false')").append(">");
			} else {
				res.append("<img ").append("src",request().getContextPath()+"/images/search.gif").append(">");
			}
			res.append("</span>");
			
			res.append("<input type='hidden' ");
			res.append("name", name).append("id", id).append("value", dispValue);
			res.append(">");
		} else if(propertyTypeInfo == PropertyTypeEnum.BOOLEAN){
			String dispValue = (propertyValue== null? "": propertyValue.toString());
			if(StringUtils.equalsIgnoreCase(dispValue, "&nbsp;")){
				dispValue = "";
			}
			boolean checked = StringUtils.equalsIgnoreCase(dispValue, "true");
			res.append("<span class='booleanInput'").append("title",col.getPromptTitle()).append("nowrap>");
			res.append("<input type='radio' ");
			res.append("name", name).append("id", id+"_T").append("value","true").append(checked?"checked":"");
			if(!StringUtils.isEmpty(col.getHtmlAttributes())) res.append(col.getHtmlAttributes());
			res.append(">").append(BOOLEAN_TRUE);
			res.append("&nbsp;&nbsp;");
			res.append("<input type='radio' ");
			res.append("name", name).append("id", id+"_F").append("value","false").append(checked?"":"checked");
			res.append(">").append(BOOLEAN_FALSE);
			res.append("</span>");
		} else if(propertyTypeInfo == PropertyTypeEnum.TEXTAREA){
			String dispValue = (propertyValue== null? "": propertyValue.toString());
			if(StringUtils.equalsIgnoreCase(dispValue, "&nbsp;")){
				dispValue = "";
			}
			res.append("<span nowrap>");
			res.append("<textarea class='commonInput' ").append("name", name).append("id", id);
			res.append("required", col.isRequired()).append(col.getHtmlAttributes()).append(" ");
			if(!StringUtils.isEmpty(col.getPromptTitle())) res.append("title", col.getPromptTitle());
			if(col.getMaxLength() > 0) res.append("maxLength", col.getMaxLength());
			res.append(">");
			res.append(dispValue);
			res.append("</textarea>");
			res.append("</span>");
		} else if(propertyTypeInfo == PropertyTypeEnum.DATETIME){
			String dispValue = (propertyValue== null? "": propertyValue.toString());
			if(StringUtils.equalsIgnoreCase(dispValue, "&nbsp;")){
				dispValue = "";
			}
			res.append("<span nowrap>");
			res.append("<input type='text' class='datetime' ").append("datatype", dataType);
			res.append("name", name).append("id", id).append("value", dispValue);
			res.append("maxValue", col.getMaxValue()).append("minValue", col.getMinValue());
			res.append("required", col.isRequired()).append("size", String.valueOf(col.getDisplayWidth()));
			if(!StringUtils.isEmpty(col.getPromptTitle())) res.append("title", col.getPromptTitle());
			if(!StringUtils.isEmpty(col.getHtmlAttributes())) res.append(col.getHtmlAttributes());
			res.append(">");
			res.append("<img ").append("src",request().getContextPath()+"/images/datetime.gif").append("style", "cursor:hand").append("onclick","showDatetimeWindow('"+id+"','"+col.getDataType()+"');return false").append(">");
			res.append("</span>");
		} else if(propertyTypeInfo == PropertyTypeEnum.FILE || propertyTypeInfo == PropertyTypeEnum.IMAGEFILE){
			name = this.namePrefix+col.getProperty()+"File"+(this.pkSuffix? String.valueOf(primaryKey): "");
			id = name+"_"+ primaryKey;
			//
			String dispValue = (propertyValue== null? "": propertyValue.toString());
			if(StringUtils.equalsIgnoreCase(dispValue, "&nbsp;")){
				dispValue = "";
			}
			res.append("<span nowrap>");
			res.append("<input type='file' class='commonInput' ");
			res.append("name", name).append("id", id).append("value", dispValue);
			res.append("maxValue", col.getMaxValue()).append("minValue", col.getMinValue());
			res.append("required", col.isRequired()).append("size", String.valueOf(col.getDisplayWidth()));
			if(!StringUtils.isEmpty(col.getPromptTitle())) res.append("title", col.getPromptTitle());
			if(!StringUtils.isEmpty(col.getHtmlAttributes())) res.append(col.getHtmlAttributes());
			res.append(">");
			res.append("</span>");
			
		} else {
			String inputType = "text";
			if(StringUtils.equalsIgnoreCase(displayType, "PASSWORD")){
				inputType = "password";
			}
			String dispValue = (propertyValue== null? "": propertyValue.toString());
			if(StringUtils.equalsIgnoreCase(dispValue, "&nbsp;")){
				dispValue = "";
			}
			res.append("<span nowrap>");
			res.append("<input type='"+inputType+"' class='commonInput' ");
			res.append("name", name).append("id", id).append("value", dispValue);
			res.append("maxValue", col.getMaxValue()).append("minValue", col.getMinValue());
			res.append("required", col.isRequired()).append("size", String.valueOf(col.getDisplayWidth()));
			res.append("dataType", dataType);
			if(col.getMaxLength() > 0){
				res.append("maxlength", col.getMaxLength());
			}
			if(!StringUtils.isEmpty(col.getPromptTitle())) res.append("title", col.getPromptTitle());
			if(!StringUtils.isEmpty(col.getHtmlAttributes())) res.append(col.getHtmlAttributes());
			res.append(">");
			res.append("</span>");
		}
		//validate errors
		ValidateErrors errors = (ValidateErrors)request().getAttribute(Constants.VALIDATE_ERRORS);
		name = name+Constants.VALIDATE_ERRORS_MSG_SUFFIX;
		res.append("<span class='whf_err_msg' ").append("id", name).append("name", name).append(">");
		if(errors != null){
			res.append(errors.get(this.boClassName + col.getProperty()));
		}
		res.append("</span>");
		return res.toString();
	}
	
}
